
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Reflection;
using System.Xml;

namespace Swaf
{
	
	/// <summary>
	/// Allows calls to be make on Object types with a method name as a string, and
	/// provides caching of the reflection objects needed to make a call on a method
	/// to help improve performance.</summary>
	/// <author>mdenman</author>
	public class DynamicCall
	{
		protected internal System.Reflection.MethodInfo m_methodCache;
		protected internal object m_onThis;
		protected internal string m_methodName;

		internal DynamicCall(){}

		public DynamicCall(string className, string methodName)
		{
			m_methodName = methodName;
			m_onThis = Cmn.callCtor(className);
		}
		public DynamicCall(string className, string methodName, object[] callParams)
		{
			
		}
		public DynamicCall(string className, string methodName, XmlNodeList callParamNodes)
		{
			m_methodName = methodName;
			System.Type[] types = new Type[callParamNodes.Count];
			object[] callParams = new object[callParamNodes.Count];
			for(int p = 0; p < types.Length; ++p)
			{
				XmlElement param = (XmlElement) callParamNodes[p];
				string type=param.GetAttribute("type");
				object val = null;
				Type thisType = null;
				//If there is a child node for this param, pass it in as the value.
				if((type == null) || type.Equals(""))
					types[p] = typeof(object);
				else
					types[p] = Type.GetType(type);
				thisType = types[p];
				if (thisType == null)
					throw new Exception(String.Format("Unable to create param type {0} from param definition {1}",type,param.OuterXml));

				string tmpVal = param.InnerXml;
				val=Serialization.deserialize(type,tmpVal);
				callParams[p] = val;
			}

			m_onThis = Cmn.callCtor(className, types, callParams);
		}
		public DynamicCall(object onThis):this(onThis, null)
		{
		}
		/// <summary>Creates a new instance of DynamicCall</summary>
		public DynamicCall(object onThis, string method)
		{
			m_onThis = onThis;
			m_methodName = method;
		}

		public DynamicCall(object onThis, string method, Type[] paramTypes)
			:this(onThis, method)
		{
			setupMethodInfo(paramTypes);
		}

		public virtual object call(params object[] callParams)
		{
			if(m_methodCache == null)
			{
				System.Type[] types; 
				if (callParams != null)
					types = new Type[callParams.Length];
				else
					types = new Type[0];
				for(int p = 0; p < types.Length; ++p)
					if(callParams[p] == null)
						types[p] = typeof(object);
					else
						types[p] = callParams[p].GetType();

				setupMethodInfo(types);
			}

			return doCall(callParams);
		}

		protected void setupMethodInfo(Type[] paramTypes)
		{
			Type clas = null;
			try
			{
				clas = m_onThis.GetType();
				m_methodCache = clas.GetMethod(m_methodName, (System.Type[]) paramTypes);
				if(m_methodCache == null)
				{
					System.Reflection.MethodInfo[] methods = clas.GetMethods();
					for (int pos = 0; pos < methods.Length; ++pos)
					{
						System.Reflection.MethodInfo m = methods[pos];
						if (m.IsPublic && m.Name.CompareTo(m_methodName) == 0 && m.GetParameters().Length == paramTypes.Length)
							m_methodCache = m;
						/*
							try
							{
								ans = m.Invoke(m_onThis, (object[]) callParams);
								calledMember = true;
								m_methodCache = m;
							}
							catch (Exception e)
							{
								lastTriedException = e;
								string x = e.Message;
								//Try the next match
							}
						if(calledMember)
							break;
						*/
					}
 
					// CQ: CUSP00037946
					// if we traverse the entire class for this method with these parameters and there
					// was no matching signature, then this rule will not be able to be called, so
					// throw and exception.
					if (m_methodCache == null)
					{
						throw new Swaf.BizRule.BadRuleCallException(string.Format("Calling method {0} in {1} with {2} parameters", m_methodName, m_onThis.GetType().Name, paramTypes.Length));
					}
				}
			}
			catch(ArgumentException e) {throwReflectionException(clas, e, paramTypes);}
			catch(TargetInvocationException e) {throw e.InnerException;}
			catch(TargetParameterCountException e) {throwReflectionException(clas, e, paramTypes);}
#if(!COMPACT_FRAMEWORK)
			catch(TargetException e) {throwReflectionException(clas, e, paramTypes);}
			catch(MethodAccessException e) {throwReflectionException(clas, e, paramTypes);}
#endif
		}
		
		public virtual object call(System.Type[] types, object[] callParams)
		{
			if (m_methodCache == null)
				setupMethodInfo(types);

			return doCall(callParams);
		}

		protected object doCall(object[] callParams)
		{
			try
			{
				if (m_methodCache != null)
					return m_methodCache.Invoke(m_onThis, callParams);
			}
			catch(ArgumentException e) {throwReflectionException(m_onThis.GetType(), e, callParams);}
			catch(TargetInvocationException e) {throw e.InnerException;}
			catch(TargetParameterCountException e) {throwReflectionException(m_onThis.GetType(), e, callParams);}
#if(!COMPACT_FRAMEWORK)
			catch(TargetException e) {throwReflectionException(m_onThis.GetType(), e, callParams);}
			catch(MethodAccessException e) {throwReflectionException(m_onThis.GetType(), e, callParams);}
#endif			

			return null;
		}

		void throwReflectionException(Type classType, Exception e, object[] callParams)
		{
			string className = (classType == null)?"(unknown class)":classType.FullName;
			string signature = (m_methodCache == null)?className + "." + m_methodName + "(" + Cmn.join(", ", callParams) + ")":m_methodCache.ToString();
			throw new Cmn.ReflectionException(e, className, signature);
		}
	}
}